package com.bugsnag.ohos;

import com.bugsnag.ohos.internal.ImmutableConfig;

import java.io.File;
import java.util.Arrays;
import java.util.HashSet;
import java.util.List;
import java.util.Set;
import java.util.UUID;

/**
 * Represents important information about an event which is encoded/decoded from a filename.
 * Currently the following information is encoded:
 * <p>
 * apiKey - as a user can decide to override the value on an Event
 * uuid - to disambiguate stored error reports
 * timestamp - to sort error reports by time of capture
 * suffix - used to encode whether the app crashed on launch, or the report is not a JVM error
 * errorTypes - a comma delimited string which contains the stackframe types in the error
 *
 * @since 2021-07-01
 */
public class EventFilenameInfo {
    private static final String STARTUP_CRASH = "startupcrash";
    private static final String NON_JVM_CRASH = "not-jvm";
    String apiKey;
    String uuid;
    long timestamp;
    String suffix;
    Set<ErrorType> errorTypes;

    /**
     * constructor
     *
     * @param apiKey
     * @param uuid
     * @param timestamp
     * @param suffix
     * @param errorTypes
     */
    public EventFilenameInfo(String apiKey, String uuid, long timestamp, String suffix, Set<ErrorType> errorTypes) {
        this.apiKey = apiKey;
        this.uuid = uuid;
        this.timestamp = timestamp;
        this.suffix = suffix;
        this.errorTypes = errorTypes;
    }

    public String getApiKey() {
        return apiKey;
    }

    public void setApiKey(String apiKey) {
        this.apiKey = apiKey;
    }

    public String getUuid() {
        return uuid;
    }

    public void setUuid(String uuid) {
        this.uuid = uuid;
    }

    public long getTimestamp() {
        return timestamp;
    }

    public void setTimestamp(long timestamp) {
        this.timestamp = timestamp;
    }

    public String getSuffix() {
        return suffix;
    }

    public void setSuffix(String suffix) {
        this.suffix = suffix;
    }

    public Set<ErrorType> getErrorTypes() {
        return errorTypes;
    }

    public void setErrorTypes(Set<ErrorType> errorTypes) {
        this.errorTypes = errorTypes;
    }

    /**
     * Generates a filename for the Event in the format
     * "[timestamp]_[apiKey]_[errorTypes]_[UUID]_[startupcrash|not-jvm].json"
     *
     * @return encoded strings
     */
    public String encode() {
        return timestamp + "_" + apiKey + "_" + DeliveryHeaders.serializeErrorTypeHeader(errorTypes)
            + "_" + uuid + "_" + suffix + ".json";
    }

    public boolean isLaunchCrashReport() {
        return suffix.equals(STARTUP_CRASH);
    }

    /**
     * generate EventFilenameInfo from event
     *
     * @param obj
     * @param apiKey
     * @param config
     * @param isLaunching
     * @return EventFilenameInfo
     */
    public static EventFilenameInfo fromEvent(Object obj, String apiKey,
                                              ImmutableConfig config, boolean isLaunching) {
        return fromEvent(obj, UUID.randomUUID().toString(), apiKey, System.currentTimeMillis(), config, isLaunching);
    }

    /**
     * generate EventFilenameInfo from event
     *
     * @param obj
     * @param uuid
     * @param apiKey
     * @param timestamp
     * @param config
     * @param isLaunching
     * @return EventFilenameInfo
     */
    public static EventFilenameInfo fromEvent(Object obj, String uuid, String apiKey, long timestamp,
                                              ImmutableConfig config, boolean isLaunching) {
        String sanitizedApiKey = apiKey;
        if (obj instanceof Event) {
            sanitizedApiKey = ((Event) obj).getApiKey();
        } else if (apiKey.isEmpty()) {
            sanitizedApiKey = config.getApiKey();
        }

        return new EventFilenameInfo(
            sanitizedApiKey,
            uuid,
            timestamp,
            findSuffixForEvent(obj, isLaunching),
            findErrorTypesForEvent(obj)
        );
    }

    /**
     * Retrieves the error types for the given event
     *
     * @param obj
     * @return error types
     */
    private static Set<ErrorType> findErrorTypesForEvent(Object obj) {
        if (obj instanceof Event) {
            return ((Event) obj).getImpl().getErrorTypesFromStackframes();
        } else {
            Set<ErrorType> set = new HashSet<>();
            set.add(ErrorType.C);
            return set;
        }
    }

    /**
     * Calculates the suffix for the given event
     */
    private static String findSuffixForEvent(Object obj, boolean launching) {
        if (obj instanceof Event) {
            Event event = (Event) obj;
            if (event.getApp().isLaunching) {
                return STARTUP_CRASH;
            }
        }
        if (launching) {
            return STARTUP_CRASH;
        }
        return "";
    }

    /**
     * Reads event information from a filename.
     *
     * @param file
     * @param config
     * @return EventFilenameInfo
     */
    public static EventFilenameInfo fromFile(File file, ImmutableConfig config) {
        return new EventFilenameInfo(
            findApiKeyInFilename(file, config),
            "", // ignore UUID field when reading from file as unused
            -1L, // ignore timestamp when reading from file as unused
            findSuffixInFilename(file),
            findErrorTypesInFilename(file)
        );
    }

    /**
     * Retrieves the error types encoded in the filename, or an empty string if this
     * information is not encoded for the given event
     *
     * @param eventFile
     */
    private static Set<ErrorType> findErrorTypesInFilename(File eventFile) {
        String name = eventFile.getName();
        int end = name.lastIndexOf("_", name.lastIndexOf("_") - 1);
        int start = name.lastIndexOf("_", end - 1) + 1;
        Set<ErrorType> set = new HashSet<>();
        if (start < end) {
            List<String> encodedValues = Arrays.asList(name.substring(start, end).split(","));
            for (ErrorType value : ErrorType.values()) {
                if (encodedValues.contains(value.desc)) {
                    set.add(value);
                }
            }
        }
        return set;
    }

    /**
     * Retrieves the api key encoded in the filename, or an empty string if this information
     * is not encoded for the given event
     */
    private static String findApiKeyInFilename(File file, ImmutableConfig config) {
        String name = file.getName();
        String suffix = "_$STARTUP_CRASH.json";
        if (name.endsWith(suffix)) {
            name = name.subSequence(0, name.length() - suffix.length()).toString();
        }
        int start = name.indexOf("_") + 1;
        int end = name.indexOf("_", start);
        String apiKey = null;

        if (start == 0 || end == -1 || end <= start) {
            apiKey = null;
        } else {
            apiKey = name.substring(start, end);
        }
        if (apiKey == null) {
            return config.getApiKey();
        }

        return apiKey;
    }

    /**
     * Retrieves the error types encoded in the filename, or an empty string if this
     * information is not encoded for the given event
     */
    private static String findSuffixInFilename(File eventFile) {
        String fileName = eventFile.getName();
        String name = fileName;
        int index = fileName.lastIndexOf(".");
        if (index != -1) {
            name = fileName.substring(0, index);
        }
        String suffix = name.substring(name.lastIndexOf("_") + 1);
        if (suffix.equals(STARTUP_CRASH) || suffix.equals(NON_JVM_CRASH)) {
            return suffix;
        } else {
            return "";
        }
    }
}
